import extentContext from '../../context.js';
import { Comment, Text, Fragment, Static, normalizeStyle, normalizeClass } from 'vue';
import emitter from '../../emitter/index.js';

import component from './component.js';
import directive from './directive.js';
import componentType from './componentType.js';
import routerPermission from './router.js';


// 初始化预设class
const CSS = `
.ex-permission-invalid.ex-permission-invalid{
  pointer-events: none;
  filter: grayscale(100%);
}
.ex-permission-invalid-abs.ex-permission-invalid-abs{
  pointer-events: none !important;
  filter: grayscale(100%) !important;
}
.ex-permission-hide.ex-permission-hide{
  z-index: -1;
  display: none;
  opacity: 0;
  visibility: hidden;
}
.ex-permission-hide-abs.ex-permission-hide-abs{
  z-index: -1 !important;
  display: none !important;
  opacity: 0 !important;
  visibility: hidden !important;
}
`;
let styleDom = document.getElementById('ex-permission-style');
if (!styleDom){
  styleDom = document.createElement('style');
  styleDom.setAttribute('id', 'ex-permission-style');
  styleDom.innerHTML = CSS;
  document.head.appendChild(styleDom);
}

/**
 * 默认安装
 */
export default {
  install(vue, options) {
    vue.config.globalProperties.$hasPermission = hasPermission;
    vue.config.globalProperties.$isAdmin = isAdmin;
    vue.use(component);
    vue.use(directive);

    if (options.enableComponentPermission){
      vue.use(componentType);
    }
    if (options.routePermission){
      vue.use(routerPermission, options.routePermission);
    }
  },
};

export {
  component as PermissionComponent,
  directive as PermissionDirective,
};


/**
 * 判断是否有权限
 * @param {String | Array} codes - 权限码
 * @param {String} logic - and, or
 * @param {Boolean | String} tip - 是否提示，true，false，String， String 时可使用变量`{codes}`, 如： '您没有{codes}权限'
 * @returns 
 */
export function hasPermission(codes, logic = 'and', tip = false) {
  let bool = _hasPermission(codes, logic);
  if (bool){
    return bool;
  }
  // 发送提示事件
  if (typeof (tip) == 'boolean' && tip){
    // 没有权限提示
    let tipResult = {
      code: 'noAccess',
      message: '未授权',
      details: codes,
    };
    emitter.post('permission.noAccess', tipResult);
  } else if(typeof (tip) == 'string'){
    let tipResult = {
      code: 'noAccess',
      message: tip.format3({ codes }),
      details: codes,
    };
    emitter.post('permission.noAccess', tipResult);
  } else {
    console.warn('无权限', codes);
  }

  return bool;
}

/**
 * 实际判断是否有权限
 */
function _hasPermission(codes, logic = 'and'){
  if (isAdmin()) {
    return true;
  }
  let userCodes = getAuthCodeList();
  if (!userCodes?.length) {
    console.warn('未获取到用户权限: context.permission.codes');
    return false;
  }
  if (typeof (codes) == 'string') {
    return userCodes.includes(codes);
  } else if (Array.isArray(codes)) {
    if (logic == 'and') {
      return codes.every(code => userCodes.includes(code));
    } else if (logic == 'or') {
      return codes.some(code => userCodes.includes(code));
    }
  } else {
    console.warn('权限验证：code为空');
    return false;
  }
}

/**
 * 是否是管理员
 * @returns {Boolean}
 */
export function isAdmin(){
  init();
  let permission = extentContext().permission;
  return permission?.authorities?.includes('ADMIN');
}

/**
 * 获取用户的权限码列表，包括角色
 */
export function getAuthCodeList(){
  init();
  let permission = extentContext().permission;
  if (permission?.authorities?.length) {
    return permission.authorities;
  }
}

/**
 * 设置权限参数
 * @param {Object} perm 
 * @param {Array} perm.codes 权限码
 * @param {Array} perm.roles 角色
 * @param {Array} perm.adminRoles 管理员角色
 */
export function setPermission(perm){
  let permission = extentContext().permission = extentContext().permission || {};
  permission.init = false;
  if (perm.codes){
    permission.codes = perm.codes;
  }
  if (perm.roles){
    permission.roles = perm.roles;
  }
  if (perm.adminRoles){
    permission.adminRoles = perm.adminRoles;
  }
}

/**
 * 初始化
 */
function init(){
  let permission = extentContext().permission;
  if (!permission){
    console.warn('未设置权限参数: context.permission');
    return;
  }
  if (permission.init){
    return;
  }

  permission.init = true;

  if (permission.adminRoles && permission.roles) {
    // permission.roles 中包含任意 permission.adminRoles 中的角色，则认为用户为管理员
    let isAdmin = permission.adminRoles.some(adminRole =>
      permission.roles.includes(adminRole)
    );

    if (isAdmin) {
      permission.authorities = ['ADMIN'];
      return;
    }
  }


  permission.authorities = [];
  if (permission.codes){
    permission.authorities = [].concat(permission.codes);
  }
  if (permission.roles){
    permission.roles.forEach(role => {
      permission.authorities.push('ROLE_' + role);
    });
  }


}

/**
 * 规范化权限配置
 * @param {String | Array | Object} auth 
 * @returns 
 */
export function normalizeAuthConfig(auth){
  let config = {};

  if (typeof (auth) == 'string') {
    config.code = auth;
  } else if (Array.isArray(auth)) {
    config.code = auth;
  } else {
    config = { ...auth };
  }

  config.tip = 'tip' in config ? config.tip : false;

  config.css = {
    class: normalizeClass(config.class) || '',
    style: normalizeStyle2Object(config.style)
  };

  if (config.noAccess == 'invalid') {
    config.css.class += ' ex-permission-invalid';

  } else if (config.noAccess == 'invalid-abs') {
    config.css.class += ' ex-permission-invalid-abs';

  } else if (config.noAccess == 'hide') {
    config.css.class += ' ex-permission-hide';

  } else if (config.noAccess == 'hide-abs') {
    config.css.class += ' ex-permission-hide-abs';
  }

  return config;
}



/**
 * 为组件添加样式
 * @param {Component} component 组件实例
 * @param {String} name 钩子函数简写
 * @param {Function} fn 执行方法
 */
export function addComponentHook(component, name, fn){
  let hook = component[name] = component[name] || [];
  if (!hook.some(it => it.name == fn.name)) {
    hook.push(fn);
  }
}

/**
 * 为Vnode添加钩子函数
 * @param {VNode} vnode 组件
 * @param {String} name 钩子函数名
 * @param {FUnction} fn 执行函数
 * @returns 
 */
export function addNodeHook(vnode, name, fn){
  let hook = vnode.type[name];
  if (hook?.fnName == fn.name){
    return;
  }

  vnode.type[name] = function(){
    fn.apply(this);
    vnode.type[name].parent && vnode.type[name].parent.apply(this, arguments);
  };
  vnode.type[name].fnName = fn.name;
  vnode.type[name].parent = hook;

}

/**
 * 获取组件节点，多个该节点的组件也能获取
 * @param {VNode | Component | Context} vnodebj 组件
 * @returns 
 */
function getElements(obj){

  let type = getVueObjectType(obj);
  let el, ctx;
  if (type == 'vnode') {
    el = obj.el;
    ctx = obj.component;
  } else if (type == 'vue') {
    el = obj.$el;
    ctx = obj.$;
  } else if (type == 'ctx') {
    el = obj.ctx.$el;
    ctx = obj;
  } else{
    console.warn('未知的vue对象类型');
    return [];
  }
  if (!el){
    return [];
  }

  if (el instanceof HTMLElement){
    return [el];
  }
  return Array.from(el.parentElement.children)
    .filter(node => node.__vnode.ctx === ctx);
}

/**
 * 获取vue对象类型
 * @param {*} obj 
 */
function getVueObjectType(obj){
  if (obj.__v_isVNode){
    return 'vnode';
  }
  if ('$options' in obj && '$' in obj &&  '$data' in obj){
    return 'vue';
  }
  if ('render' in obj && 'slots' in obj && 'vnode' in obj){
    return 'ctx';
  }
}

/**
 * 以对象方式输出style
 * @param {*} value 
 */
export function normalizeStyle2Object(value){
  if (typeof (value) == 'string'){
    value = [value];
  }
  return normalizeStyle(value);

}

/**
 * 为组件添加样式
 * @param {*} vnodes 
 * @param {Object} css.style 样式
 * @param {String} css.class 样式
 * @returns 
 */
export function addVueCss(vnodes, css) {
  vnodes.forEach(vnode => {
    vnode = getVnode(vnode);
    if ([Fragment, Static, Comment, Text].includes(vnode.type)) return;

    if (vnode.el) {
      let els = getElements(vnode);
      els?.forEach(el => {
        el.classList.add(...(css.class.split(' ')).filter(Boolean));
        Object.assign(el.style, css.style);
      });

    } else {

      if (css.class) {
        if (vnode.props.class) {
          vnode.props.class += ' ' + css.class;
        } else {
          vnode.props.class = css.class;
        }
      }

      if (css.style) {
        if (vnode.props.style) {
          Object.assign(vnode.props.style, css.style);
        } else {
          vnode.props.style = css.style;
        }
      }

      if (vnode.component) {
        addComponentHook(vnode.component, 'm', function pushVueCss() {
          addVueCss([vnode], css);
        });
        addComponentHook(vnode.component, 'u', function pushVueCss() {
          addVueCss([vnode], css);
        });
      } else {
        addNodeHook(vnode, 'mounted', function pushVueCss() {
          addVueCss([vnode], css);
        });
        addNodeHook(vnode, 'updated', function pushVueCss() {
          addVueCss([vnode], css);
        });
      }
    }

  });
  return vnodes;
}

/**
 * 获取Vnode
 */
function getVnode(obj) {
  let type = getVueObjectType(obj);
  if (type == 'vnode') {
    return obj;
  } else if (type == 'ctx') {
    return obj.vnode;
  } else if (type == 'vue') {
    return obj.$.vnode;
  }
}